﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using YooAsset;
using Cysharp.Threading.Tasks;
/// <summary>
/// 针对Addressables的资源管理脚本
/// </summary>
public class ResourceManager
{
    public static int NullOwnerId = Int32.MaxValue; 
    #region 非托管加载Load资源
    //统一加载的缓存
    private static Dictionary<string, ReferenceObj> dicReferenceObj = new Dictionary<string, ReferenceObj>();
    private static UnityEngine.Object GetObject(string address)
    {
        ReferenceObj referenceObj;
        if(dicReferenceObj.TryGetValue(address,out referenceObj))
        {
            return referenceObj.GetObject();
        }
        return null;
    }
    /// <summary>
    /// 资源异步非托管加载
    /// </summary>
    /// <param name="address"></param>
    /// <param name="onComplete"></param>
    private static void Load<T>(string address, Action<T> onComplete) where T : UnityEngine.Object
    {
        UnityEngine.Object obj = GetObject(address);
        if(obj != null)
        {
            onComplete?.Invoke((T)obj);
            return ;
        }
        AssetOperationHandle handle = YooAssets.LoadAssetAsync<T>(address);
        handle.Completed += (obj) =>
        { 
            if (handle.AssetObject != null) {
                UnityEngine.Object refObj = AddRefObj(address, handle);
                onComplete?.Invoke((T)refObj);
            }
            else
            {
                Debug.LogError("资源加载异常！！！,资源名字:" + address);
                onComplete?.Invoke(null);
            } 
        };
    }
    private static UnityEngine.Object AddRefObj(string address, AssetOperationHandle handle)
    {
        ReferenceObj referenceObj;
        if (dicReferenceObj.TryGetValue(address,out referenceObj))
        {
            //因为是重复的所以新加载的直接释放，用旧的
            handle.Release();
            return referenceObj.GetObject();
        }
        dicReferenceObj.Add(address, new ReferenceObj(handle));
        return handle.AssetObject;
    }
    /// <summary>
    /// 同步加载非托管资源
    /// </summary>
    /// <param name="address"></param>
    /// <param name="owner"></param>
    /// <param name="refrenceCheckType"></param>
    /// <returns></returns>
    private static T LoadSync<T>(string address) where T : UnityEngine.Object
    {
        UnityEngine.Object obj = GetObject(address);
        if (obj != null)
        { 
            return (T)obj;
        }
        AssetOperationHandle handle = YooAssets.LoadAssetSync<T>(address);
        //handle.WaitForAsyncComplete();
        if (handle.AssetObject != null)
        {
            UnityEngine.Object refObj = AddRefObj(address, handle);
            return (T)refObj;
        }
        else
        {
            Debug.LogError("资源加载异常！！！,资源名字:" + address);
        } 
        return null;
    }
    public static ReferenceObj GetAAReferenceObj(string address)
    {
        ReferenceObj referenceObj;
        if (dicReferenceObj.TryGetValue(address,out referenceObj))
        {
            return referenceObj;
        }
        return null;
    }
    #endregion

    #region 释放非托管资源
    /// <summary>
    /// 注意这个方法禁止个人访问，不然会照成引用计算错误！释放非托管资源，引用计数-1
    /// </summary>
    /// <param name="address"></param>
    public static void Release(string address)
    {
        ReferenceObj referenceObj;
        if(dicReferenceObj.TryGetValue(address,out referenceObj))
        {
            referenceObj.Release();
        }
    }
    #endregion

    #region 托管资源加载
    private static Dictionary<int, AutoReferenceObj> dicAutoReferenceObj = new Dictionary<int, AutoReferenceObj>();
    private static UnityEngine.Object GetAutoObject(UnityEngine.Object parent, string address)
    {
        int id = parent == null ? NullOwnerId : parent.GetInstanceID();
        AutoReferenceObj referenceObj;
        if (dicAutoReferenceObj.TryGetValue(id,out referenceObj))
        {
            //先判断是否重复加载
            UnityEngine.Object obj = referenceObj.GetObject(address);
            if(obj != null)
            {
                return obj;
            }
        }
        return null;
    }

    /// <summary>
    /// 异步托管实例化
    /// </summary>
    /// <param name="address"></param>
    /// <param name="parent"></param>
    /// <param name="onComplete"></param>
    public static void Instantiate(string address, Transform parent, Action<GameObject> onComplete)
    {
        //先判断是否重复加载
        UnityEngine.Object obj = GetAutoObject(parent, address);
        if(obj != null)
        {
            onComplete?.Invoke((GameObject)GameObject.Instantiate(obj,parent));
            return;
        }
        //如果不是重复加载去统一加载的缓存去找，如果找到就添加引用
        obj = GetObject(address);
        if (obj != null)
        {
            //添加到自动托管
            AddAutoReferenceObj(address, parent, obj);
            onComplete?.Invoke((GameObject)GameObject.Instantiate(obj, parent));
            return;
        }
        //异步实例化
        Load<GameObject>(address, (obj) => 
        {
            if(obj != null)
            {
                AddAutoReferenceObj(address, parent, obj);
                onComplete?.Invoke((GameObject)GameObject.Instantiate(obj, parent));
            }
        });
    }

    /// <summary>
    /// 同步托管实例化
    /// </summary>
    /// <param name="address"></param>
    /// <param name="onComplete"></param>
    public static GameObject InstantiateSync(string address, Transform parent)
    {
        UnityEngine.Object obj = GetAutoObject(parent, address);
        if (obj != null)
        {
            return (GameObject)GameObject.Instantiate(obj, parent);
        }
        //如果不是重复加载去统一加载的缓存去找，如果找到就添加引用
        obj = GetObject(address);
        if (obj != null)
        {
            //添加到自动托管
            AddAutoReferenceObj(address, parent, obj);
            return (GameObject)GameObject.Instantiate(obj, parent);
        }
        GameObject goObj = LoadSync<GameObject>(address);
        if (goObj != null)
        {
            AddAutoReferenceObj(address, parent, obj); 
            return (GameObject)GameObject.Instantiate<GameObject>(goObj, parent);
        }
        return null;
    }

    /// <summary>
    /// 托管异步加载
    /// </summary>
    /// <param name="address"></param>
    /// <param name="parent"></param>
    /// <param name="onComplete"></param>
    public static void Load<T>(string address,UnityEngine.Object parent, Action<T> onComplete) where T : UnityEngine.Object
    {
        //判断是否重复加载
        UnityEngine.Object obj1 = GetAutoObject(parent, address);
        if (obj1 != null)
        {
            onComplete?.Invoke((T)obj1);
            return;
        }
        //如果不是重复加载去统一加载的缓存去找，如果找到就添加引用
        obj1 = GetObject(address);
        if (obj1 != null)
        {
            //添加到自动托管
            AddAutoReferenceObj(address, parent, obj1);
            onComplete?.Invoke((T)obj1);
            return;
        }
        //只能从磁盘加载
        Load<T>(address, (obj) =>
        {
            if (obj != null)
            {
                AddAutoReferenceObj(address, parent, obj);
                onComplete?.Invoke(obj);
            }
        });
    }
    /// <summary>
    /// 托管同步加载
    /// </summary>
    /// <param name="address"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static T LoadSync<T>(string address, UnityEngine.Object parent) where T : UnityEngine.Object
    {
        //判断是否重复加载
        UnityEngine.Object obj1 = GetAutoObject(parent, address);
        if (obj1 != null)
        { 
            return (T)obj1;
        }
        //如果不是重复加载去统一加载的缓存去找，如果找到就添加引用
        obj1 = GetObject(address);
        if (obj1 != null)
        {
            //添加到自动托管
            AddAutoReferenceObj(address, parent, obj1);
            return (T)obj1;
        }
        //只能从磁盘加载
        T obj = LoadSync<T>(address);
        if (obj != null)
        {
            AddAutoReferenceObj(address, parent, obj);
            return obj;
        }
        return null;
    }
    /// <summary>
    /// 自动引用添加
    /// </summary>
    /// <param name="address"></param>
    /// <param name="parent"></param>
    /// <param name="obj"></param>
    private static void AddAutoReferenceObj(string address, UnityEngine.Object parent, UnityEngine.Object obj)
    {
        AutoReferenceObj referenceObj;
        int id = parent == null? NullOwnerId : parent.GetInstanceID();
        if (!dicAutoReferenceObj.TryGetValue(id, out referenceObj))
        {
            referenceObj = new AutoReferenceObj(parent);
            dicAutoReferenceObj.Add(referenceObj.GetOwnerId(), referenceObj);
        }
        referenceObj.AddObject(address, obj);
    }

    #endregion

    #region 释放托管资源
    private static async UniTask FremeEndRelease(Action call, bool containsAsset = false)
    {
        await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
        try
        {
            List<int> autoKeys = dicAutoReferenceObj.GetkeyList();
            foreach (int id in autoKeys)
            {
                AutoReferenceObj referenceObj;
                if (dicAutoReferenceObj.TryGetValue(id, out referenceObj))
                {
                    //释放资源
                    if (referenceObj.CheckDestroy())
                    {
                        dicAutoReferenceObj.Remove(id);
                    }
                }
            }
            //释放非托管资源（也就是引用资源）
            List<string> refKeys = dicReferenceObj.GetkeyList();
            foreach (string addres in refKeys)
            {
                ReferenceObj referenceObj;
                if (dicReferenceObj.TryGetValue(addres, out referenceObj))
                {
                    if (referenceObj.CheckDestroy())
                    {
                        dicReferenceObj.Remove(addres);
                    }
                }
            }
            //释放从Assets加载的资源
            if (containsAsset)
            {
                var package = YooAssets.GetPackage(Utils.GetBuildConfig().PackageName);
                package.UnloadUnusedAssets();
                _ = Resources.UnloadUnusedAssets();
            }
        }
        catch (Exception e)
        {
            Debug.LogError("Message:" + e.Message + ",StackTrace:"+e.StackTrace);
        }
        finally
        {
            call?.Invoke();
            if (Application.isEditor)
            {
                string datas = "";
                foreach (AutoReferenceObj referenceObj in dicAutoReferenceObj.Values)
                {
                    datas += referenceObj.ToString();
                }
                Debug.LogWarning("剩余托管资源数量："+ dicAutoReferenceObj.Count+",托管资源数据："+ datas);

                datas = "";
                foreach (ReferenceObj referenceObj in dicReferenceObj.Values)
                {
                    datas += referenceObj.ToString();
                }
                Debug.LogWarning("剩余非资源数量：" + dicReferenceObj.Count + ",非托管资源数据：" + datas);
            }
        }
    }
    /// <summary>
    /// 检测资源哪些已经没有引用可以释放！，注意这个东西可能很耗时，建议在切换加载进度条的时候使用。
    /// </summary>
    /// <param name="call">执行完成之后调用</param>
    public static void CheckRelease(Action call = null, bool containsAsset = false)
    {
        //这个问题在于GameObject.Destroy对象并不是立即被置空需要在本帧结束，下一帧渲染之前，才会被置空,所以在下一帧执行
        _ = FremeEndRelease(call, containsAsset);
    } 
    #endregion
}
